package actor

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"sync/atomic"
	"time"

	"github.com/AsynkronIT/protoactor-go/actor"
)

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
type HttpTaskID = uint64
type HttpTaskTag = uint64

type OnHttpTaskPrepare func() (request *http.Request, err error)
type OnHttpTaskCompleted func(err error, task *HttpTask)

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
type HttpTask struct {
	err         error
	id          HttpTaskID
	tag         HttpTaskTag
	request     *http.Request
	response    *http.Response
	client      *http.Client
	onCompleted OnHttpTaskCompleted
}

func (t HttpTask) ID() HttpTaskID {
	return t.id
}

func (t HttpTask) Tag() HttpTaskTag {
	return t.tag
}

func (t HttpTask) GetResponseBodyData() (body []byte, err error) {
	return ioutil.ReadAll(t.response.Body)
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
type httpTaskCompleted struct {
	id  HttpTaskID
	tag HttpTaskTag
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
type HttpTaskManager struct {
	idGenerator HttpTaskID
	tasks       map[HttpTaskID]*HttpTask
	pid         *actor.PID
}

func NewHttpTaskManager(pid *actor.PID) *HttpTaskManager {
	return &HttpTaskManager{
		idGenerator: 0,
		tasks:       map[HttpTaskID]*HttpTask{},
		pid:         pid,
	}
}

func (m *HttpTaskManager) nextID() HttpTaskID {
	return atomic.AddUint64(&m.idGenerator, 1)
}

func (m *HttpTaskManager) NewTask(timeout time.Duration, tag HttpTaskTag, onPrepare OnHttpTaskPrepare, onCompleted OnHttpTaskCompleted) (taskID HttpTaskID, err error) {
	if timeout <= 0 {
		err = ErrHttpTaskTimeoutMustGreaterThanZero
		return
	}

	request, e := onPrepare()
	if e != nil {
		err = fmt.Errorf("onPrepare fail, %w", e)
		return
	}
	if request == nil {
		err = ErrHttpTaskRequestMustNotNil
		return
	}

	if onCompleted == nil {
		err = ErrHttpTaskOnCompletedMustNotNil
		return
	}

	client := &http.Client{Timeout: timeout}

	taskID = m.nextID()

	task := &HttpTask{
		id:          taskID,
		tag:         tag,
		request:     request,
		client:      client,
		onCompleted: onCompleted,
	}

	m.tasks[task.id] = task

	go func() {
		task.response, task.err = task.client.Do(task.request)
		RootContext.Send(m.pid, &httpTaskCompleted{id: task.id, tag: task.tag})
	}()

	return
}

func (m *HttpTaskManager) OnCompleted(id HttpTaskID) (err error) {
	task, ok := m.tasks[id]
	if !ok {
		err = ErrHttpTaskNotFound
		return
	}

	task.onCompleted(task.err, task)

	defer func() {
		delete(m.tasks, id)
	}()

	if task.err == nil {
		err = task.response.Body.Close()
		if err != nil {
			err = fmt.Errorf("close response body fail, %w", err)
			return
		}
	}

	return
}
